/*-
 * Copyright 2012 Guram Dukashvili
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
//---------------------------------------------------------------------------
#include "sys.h"
#include "str.h"
#include "array.h"
#include "cursor.h"
#include "thread.h"
//---------------------------------------------------------------------------
struct test_shared_data {
  array * ids;
  struct index * idx;
  struct cursor * c;
  struct rand * rnd;
  int64_t start_time;
  int64_t sub_time;
  int64_t ellapsed_time;
  struct mutex mtx;
  struct semaphore go;
  uintptr_t threads;
  uint32_t i, lb;
};
//---------------------------------------------------------------------------
static void test_shared_data_init(struct test_shared_data * data)
{
  uint32_t i;

  data->ids = array_new();
  array_set_element_size(data->ids,sizeof(uint32_t),0);

  for( i = 0; i < 20000000; i++ )
    be32enc(array_append_get(data->ids),i);
  data->threads = 1;

  data->idx = index_new();
  data->c = cursor_new();

  data->rnd = rand_new();
#ifndef NDEBUG
  rand_srand(data->rnd,1,1,1);
#else
  rand_srand(data->rnd,get_time(),get_time() << 5,(get_time() << 5) + get_time());
#endif
  mutex_init(&data->mtx);
  semaphore_init(&data->go);

  data->idx->id = 1;
  string_copy_c(&data->idx->data_dir,"storage",0);
  index_set_key_size(data->idx,data->ids->element_size);
  index_set_data_size(data->idx,sizeof(uint32_t));
  index_set_block_index_size(data->idx,3);

  index_drop(data->idx);

  data->c->idx = data->idx;
}
//---------------------------------------------------------------------------
static void test_shared_data_destroy(struct test_shared_data * data)
{
  semaphore_destroy(&data->go);
  mutex_destroy(&data->mtx);
  sfree(data->rnd);
  cursor_delete(data->c);
  index_delete(data->idx);
  array_delete(data->ids);
}
//---------------------------------------------------------------------------
static void multithread_insert_test_entry_callback(struct test_shared_data * data)
{
  uint32_t j, d = 0xCDCDCDCD;
  struct cursor c;
  byte_size_int * block_index = alloca(data->idx->block_index_size);
  memset(block_index,0,data->idx->block_index_size);

  cursor_init(&c);
  c.idx = data->idx;

  for( j = data->lb; j <= data->i; j++ ){
    //const void * key = array_get(data->ids,j);
    uint32_t d_sel;
    //struct index_link * root = index_link_block(data->idx,block_index);
    //intptr_t c = DEBUG_index_lookup_key(data->idx,root,block_index,key);
    cursor_select_key(&c,array_get(data->ids,j),&d_sel);

    if( errno == ENOENT || d != d_sel ){
      //c = DEBUG_index_lookup_key_dump(data->idx,root,block_index,key);
      fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
      abort();
    }
    //index_unlink_block(data->idx,root,RWL_NONE,RWL_NONE);
  }

  cursor_destroy(&c);
}
//---------------------------------------------------------------------------
static void multithread_insert_test_entry(struct thread * thr,struct test_shared_data * data,uintptr_t id)
{
  uint32_t i, d = 0xCDCDCDCD,
    cnt = data->ids->count / data->threads,
    lb = cnt * id, rb = lb + (id + 1 < data->threads ? cnt : data->ids->count - lb);
  struct cursor c;
  byte_size_int * block_index = alloca(data->idx->block_index_size);
  memset(block_index,0,data->idx->block_index_size);

  cursor_init(&c);
  c.idx = data->idx;

  thr = thr;
  semaphore_wait(&data->go);

#ifndef NDEBUG
  data->lb = lb;
  data->idx->callback = multithread_insert_test_entry_callback;
  data->idx->callback_object = data;
#endif

  for( i = lb; i < rb; i++ ){
    data->i = i;
    cursor_insert_key(&c,array_get(data->ids,i),&d);

    //if( (i + 1) % (cnt / 8) == 0 || i + 1 == rb ){
    //  uintptr_t lnk;
    //  struct index_link * root = index_link_block(data->idx,block_index);
    //  array changed_links;

    //  array_init_copy(&changed_links,&data->idx->changed_links,0);
    //  rwlock_acquire_write_lock(&root->lock);
    //  for( lnk = 0; lnk < changed_links.count; lnk++ ){
    //    struct index_link_change * p_link = array_get(&changed_links,lnk);
    //    index_scatter_block(data->idx,root,p_link->link);
    //  }
    //  array_copy(&changed_links,&data->idx->changed_links);
    //  index_unlink_block(data->idx,root,RWL_WRITE,RWL_NONE);
    //  array_resize(&data->idx->changed_links,0);
    //  array_destroy(&changed_links);
    //  multithread_insert_test_entry_callback(data);
    //}

    //if( (i + 1) % (cnt / 16) == 0 || i + 1 == rb ){
    //  int64_t t = get_time();

    //  //index_write_journal(data->idx);
    //  //index_shrink_cache_lru(data->idx,false);

    //  multithread_insert_test_entry_callback(data);

    //  t = get_time() - t;
    //  mutex_acquire(&data->mtx);
    //  data->sub_time += t;
    //  mutex_release(&data->mtx);
    //}
  }

  cursor_destroy(&c);
}
//---------------------------------------------------------------------------
static void run_multithread_insert_test(struct test_shared_data * data)
{
  intptr_t i;
  struct thread_pool pool;

  thread_pool_init(&pool);

  for( i = data->threads - 1; i >= 0; i-- )
    thread_pool_spare(&pool,multithread_insert_test_entry,data,i);

  semaphore_post_v(&data->go,data->threads);
  thread_pool_wait(&pool);

  thread_pool_destroy(&pool);
}
//---------------------------------------------------------------------------
int main(int argc,char ** argv)
{
#if !defined(NDEBUG) && defined(_MSC_VER)
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF /*| _CRTDBG_CHECK_ALWAYS_DF *//*| _CRTDBG_CHECK_CRT_DF*/ /*| _CRTDBG_DELAY_FREE_MEM_DF*/);
  _CrtSetReportMode(_CRT_ERROR,_CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW);
#endif
  argc = argc;
  argv = argv;

#if COMPILE_RUNTIME_CHECKS
  {
    portable_stat_t st;
    if( portable_stat(argv[0],&st) != 0 ){
      fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
    }
  }
#endif

  microacid_system_test_and_init();

  print_new_signature();

  thread_locking_test(system_cores() + 1,system_cores() + 1,1024);

  {
    string s, ellapsed;
    uint32_t i, /*j,*/ d = 0xCDCDCDCD;
    struct test_shared_data * data = salloc(sizeof(struct test_shared_data),NULL);
    byte_size_int * root_block_index = alloca(BYTE_SIZE_INT_MAX);
    memset(root_block_index,0,BYTE_SIZE_INT_MAX);

    //----------------------------------------------------
    // data initialization
    //----------------------------------------------------

    string_init(&s);
    string_init(&ellapsed);

    test_shared_data_init(data);

    array_shuffle(data->ids,data->rnd,data->ids->count * 3);

    //----------------------------------------------------
    // insert test
    //----------------------------------------------------

    get_time_string(&s,get_local_time());
    fprintf(stderr,"insert test started %s\n",s.s);
    data->sub_time = 0;
    data->start_time = get_time();

    run_multithread_insert_test(data);

    data->ellapsed_time = get_time() - data->start_time - data->sub_time;
    get_time_code_string(&ellapsed,data->ellapsed_time);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"insert test finished %s, ellapsed: %s\n",s.s,ellapsed.s);
    fprintf(stderr,"%.4lf ips\n",data->ids->count / ((double) (data->ellapsed_time + 1) / 1000000));
    fprintf(stderr,"cache hit ratio: %.4lf%%\n",data->idx->cache->hit_count * 100. / (data->idx->cache->hit_count + data->idx->cache->miss_count));
    get_time_code_string(&ellapsed,data->idx->cache->iowait_time);
    fprintf(stderr,"cache IO wait time: %s, %.4lf%%\n",ellapsed.s,data->idx->cache->iowait_time * 100. / data->ellapsed_time);
    data->idx->cache->iowait_time = 0;

    //----------------------------------------------------
    // select test
    //----------------------------------------------------
    array_shuffle(data->ids,data->rnd,data->ids->count * 3);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"select test started %s\n",s.s);
    data->start_time = get_time();

    for( i = 0; i < data->ids->count; i++ ){
      uint32_t d_sel;
      cursor_select_key(data->c,array_get(data->ids,i),&d_sel);
      if( errno == ENOENT || d != d_sel ){
        fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
        abort();
      }
    }

    data->ellapsed_time = get_time() - data->start_time;
    get_time_code_string(&ellapsed,data->ellapsed_time);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"select test finished %s, ellapsed: %s\n",s.s,ellapsed.s);
    fprintf(stderr,"%.4lf sps\n",data->ids->count / ((double) (data->ellapsed_time + 1) / 1000000));
    fprintf(stderr,"cache hit ratio: %.4lf%%\n",data->idx->cache->hit_count * 100. / (data->idx->cache->hit_count + data->idx->cache->miss_count));
    get_time_code_string(&ellapsed,data->idx->cache->iowait_time);
    fprintf(stderr,"cache IO wait time: %s, %.4lf%%\n",ellapsed.s,data->idx->cache->iowait_time * 100. / data->ellapsed_time);
    data->idx->cache->iowait_time = 0;

    //----------------------------------------------------
    // update test
    //----------------------------------------------------

    d = ~d;
    get_time_string(&s,get_local_time());
    fprintf(stderr,"update test started %s\n",s.s);
    data->sub_time = 0;
    data->start_time = get_time();

    for( i = 0; i < data->ids->count; i++ ){
      cursor_update_key(data->c,array_get(data->ids,i),&d);
      if( errno == ENOENT ){
        fprintf(stderr,"%u, undefined behavior in %s, line %d\n",i,__FILE__,__LINE__);
        abort();
      }

    //  if( (i + 1) % (data->ids->count / 16) == 0 || i + 1 == data->ids->count ){
    //    int64_t t = get_time();
    //    //index_write_journal(data->idx);
    //    for( j = 0; j <= i; j++ ){
    //      uint32_t d_sel;
    //      cursor_select_key(data->c,array_get(data->ids,j),&d_sel);
    //      if( errno == ENOENT || d != d_sel ){
    //        fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
    //        abort();
    //      }
    //    }
    //    data->sub_time += get_time() - t;
    //  }
    }

    data->ellapsed_time = get_time() - data->start_time - data->sub_time;
    get_time_code_string(&ellapsed,data->ellapsed_time);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"update test finished %s, ellapsed: %s\n",s.s,ellapsed.s);
    fprintf(stderr,"%.4lf ups\n",data->ids->count / ((double) (data->ellapsed_time + 1) / 1000000));
    fprintf(stderr,"cache hit ratio: %.4lf%%\n",data->idx->cache->hit_count * 100. / (data->idx->cache->hit_count + data->idx->cache->miss_count));
    get_time_code_string(&ellapsed,data->idx->cache->iowait_time);
    fprintf(stderr,"cache IO wait time: %s, %.4lf%%\n",ellapsed.s,data->idx->cache->iowait_time * 100. / data->ellapsed_time);
    data->idx->cache->iowait_time = 0;

    //----------------------------------------------------
    // delete test
    //----------------------------------------------------
    array_shuffle(data->ids,data->rnd,data->ids->count * 3);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"delete test started %s\n",s.s);
    data->sub_time = 0;
    data->start_time = get_time();

    for( i = 0; i < data->ids->count; i++ ){
      uint32_t d_sel;

      //{
      //  uint32_t key = htobe32(0x00016D0C);
      //  struct index_link * root = index_link_block(data->idx,root_block_index);
      //  intptr_t c = DEBUG_index_lookup_key(data->idx,root,&root->block_index,&key);
      //  if( c != 0 ){
      //    c = DEBUG_index_lookup_key_dump(root->idx,root,&root->block_index,&key);
      //  }
      //  index_unlink_block(data->idx,root,RWL_NONE,RWL_NONE);
      //}

      cursor_delete_key(data->c,array_get(data->ids,i),&d_sel);
      if( errno == ENOENT || d != d_sel ){
        fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
        abort();
      }

      //if( (i + 1) % (data->ids->count / 16) == 0 || i + 1 == data->ids->count ){
      //  int64_t t = get_time();
      //  //index_write_journal(data->idx);
      //  for( j = i + 1; j < data->ids->count; j++ ){
      //    uint32_t d_sel;
      //    cursor_select_key(data->c,array_get(data->ids,j),&d_sel);
      //    if( errno == ENOENT || d != d_sel ){
      //      fprintf(stderr,"undefined behavior in %s, line %d\n",__FILE__,__LINE__);
      //      abort();
      //    }
      //  }
      //  data->sub_time += get_time() - t;
      //}
    }

    data->ellapsed_time = get_time() - data->start_time - data->sub_time;
    get_time_code_string(&ellapsed,data->ellapsed_time);
    get_time_string(&s,get_local_time());
    fprintf(stderr,"delete test finished %s, ellapsed: %s\n",s.s,ellapsed.s);
    fprintf(stderr,"%.4lf dps\n",data->ids->count / ((double) (data->ellapsed_time + 1) / 1000000));
    fprintf(stderr,"cache hit ratio: %.4lf%%\n",data->idx->cache->hit_count * 100. / (data->idx->cache->hit_count + data->idx->cache->miss_count));
    get_time_code_string(&ellapsed,data->idx->cache->iowait_time);
    fprintf(stderr,"cache IO wait time: %s, %.4lf%%\n",ellapsed.s,data->idx->cache->iowait_time * 100. / data->ellapsed_time);

    //----------------------------------------------------
    // shutdown
    //----------------------------------------------------

    test_shared_data_destroy(data);
    sfree(data);

    string_destroy(&ellapsed);
    string_destroy(&s);
  }

  microacid_system_destroy();
  return 0;
}
//---------------------------------------------------------------------------
